9章 擬似コードによるプログラミング
9.3.1 akht.icon
プログラミングの細部に焦点を合わせ、ここのクラスやそのルーチンを作成する手順を具体的に見ていく
PPP ( Pseudo Programming Process )
9.1 クラスとルーチンの作成手順の概要
大体は反復的なプロセス
https://gyazo.com/277591b0ac4a3faa82c35f9028cf0c7c
9.1.1 クラスの作成手順
クラス全体を設計する
クラスの責任を明確に定義する
クラスにどのような「秘密」が隠されるのかを定義する
クラスのインターフェイスがどのような抽象化を実現するのかを定義する
他のクラスを継承させるのか、他のクラスからの継承を許可するのかを決定する
クラスの重要なパブリックメソッドとクラスが使用する重要なメンバをすべて特定し、設計する
これらを繰り返し検討する
クラスの各ルーチンを作成する
最初の手順で特定したルーチンを実際に作成する
だいたい最初のルーチンだけじゃ足りなくて、追加でルーチンを作ることになる
その追加ルーチンの作成に伴うさまざまな問題が、クラス全体の設計にまで波及することがよくある
クラス全体をレビューし、テストする
ルーチンは作成した時にテストする
クラス全体が完成したら、クラス全体をレビューして、個々のルーチンレベルではテストできない問題をテストする
9.1.2 ルーチンの作成手順
クラスのルーチンというのはたいていは単純であり実装も簡単なものが多い
しかし実装するのが複雑なルーチンもある
それらを作成するためには、体系的なアプローチが役立つ
https://gyazo.com/bad1ae25800d60a35d5a5f76e5a18f46
9.2 擬似コードプログラミングプロセス ( PPP )
「擬似コード」という用語は、アルゴリズム、ルーチン、クラス、またはプログラムのしくみを説明するための略式の表記法を指す。
擬似コードを効果的に使うためのガイドライン
特定の処理を正確に説明する文章を使用する
プログラミング言語の構文要素を使用しない
コードそのものよりも少し上位レベルで設計を行いたいので、プログラミング言語の構文を使うことで意識が詳細レベルに下がってしまうことを避ける
意味もなく構文上の制限に縛られてしまうのを避ける
目的のレベルで擬似コードを書く
その方法をプログラミング言語でどのように実装するかではなく、その方法をとることの意味を説明する
ほぼそのままコーディングできるくらいの詳細レベルで擬似コードを書く
概略すぎてもダメ(問題をはらんでいる部分がうまく誤魔化されてしまうおそれがある)
コードを簡単に書き起こせるレベルまで擬似コードの細部を煮詰める
擬似コードを使えばコードもコメントも書けて一石二鳥
擬似コードを書く
それをもとにコードを書く
擬似コードはコメントとして添える
ガイドラインに従って擬似コードを作成すれば、コメントは完璧に意味の通るものになる
ガイドラインに違反して擬似コードを使った例
code: bad pseudo code
リソース数を1つインクリメントする
malloc()を使ってdlg構造体を割り当てる
malloc()がNULLを返したら、1を返す
OSrsrc_initを呼び出し、オペレーティングシステムのリソースを初期化する
*hRsrcPtr = リソース数
0を返す
この擬似コードは何を伝えたいのか、うまく書かれていないのでほとんどわからない
これの問題点は、*hRsrcPtr(C言語固有のポインタ表記)やmalloc()(C言語固有の関数)など、プログラミング言語のコーディング要素を含んでいること
この擬似コードは、設計の意味ではなくコーディングの方法に焦点があたっている
ルーチンが1を返すとか0を返すといったコーディングの詳細に踏み込んでいる
適切なコメントか?という観点で考えると、この擬似コードが役に立たないのはあきらか
良い擬似コードの例
code: good pseudo code
使用しているリソースの数を記録する
if 別のリソースが利用できる
ダイアログボックス構造体を割り当てる
if ダイアログボックス構造体の割り当てが可能である
使用するリソースが1つ増えることを記録する
リソースを初期化する
呼び出し元から提供された場所にリソースの数を格納する
Endif
Endif
新しいリソースが作成された場合はtrueを返し、そうでない場合はfalseを返す
この擬似コードは普通の文章で書かれていて、プログラミング言語の構文要素を1つも使っていない
bad pseudo codeの方はC言語でしか実装できないが、こちらは言語の選択を制限しない
わかりやすい文章で書かれているにもかかわらず、プログラミング言語のコードベースとして簡単に使用できるほど正確で詳細
擬似コードの文章をコメントに転用すれば、コードの意図を十分に説明するものになる
このスタイルの擬似コードを使うことにどんな利点があるか?
擬似コードはレビューを容易にする
擬似コードは反復型の詳細化を後押しする
上位レベルの設計 → 設計を擬似コードに詳細化 → 擬似コードをソースコードに詳細化
徐々に改良を重ねることで、下位レベルの詳細を詰めながら設計を確認することができる
それぞれのレベルでエラーを捕捉することができる
擬似コードは変更を容易にする
設計図の線を1本引き直すのと、ツーバイフォーの壁をはがして別の場所に打ち付けるのでは、どちらが良いだろうか。
擬似コードはコメントの作成を最小限に抑える
擬似コードは他の形式の設計書よりも管理しやすい
擬似コードは詳細設計のためのツールとして申し分がない
9.3 PPPを使ったルーチンの作成
ルーチンの作成に必要な次の作業について説明
ルーチンの設計
ルーチンのコーディング
コードの検査
残りの仕上げ作業
必要に応じて繰り返す
9.3.1 ルーチンの設計
ルーチンを洗い出したら、それを設計する
【例】エラーコードに基づいてエラーメッセージを出力するルーチン ReportErrorMessage()
仕様
入力引数としてエラーコードを受け取り、そのエラーコードに対応するエラーメッセージを出力する
無効なコードにも対処する
プログラムを対話モードで実行している場合、このルーチンはユーザーにメッセージを表示する
コマンドラインモードで実行している場合、このルーチンはメッセージをメッセージファイルに記録する
メッセージを出力した後、成功または失敗を示す状態値を返す
ルーチンの設計方法
1. 準備が整っていることを確認する
ルーチンの役割がきちんと定義されていて、全体的な設計と整合していることを確認する
ルーチンが実際に必要であることを確認する
2. ルーチンが解決する問題を定義する
ルーチンが作成できるくらい詳細に説明する
高レベルの設計では少なくとも次の点を明確にする
ルーチンが隠蔽する情報
ルーチンへの入力
ルーチンからの出力
事前条件
事後条件
ReportErrorMessage()ではこれらの点に次のように対処する
ルーチンは、エラーメッセージのテキストと現在の処理方法(対話型またはコマンドライン)のん2つを隠蔽する
ルーチンに保証される事前条件はない
ルーチンへの入力はエラーコードである
2種類の出力が要求される
エラーメッセージ
呼び出し元に返す状態値
ルーチンは状態値がSuccessかFailureのどちらかになることを保証する
3. ルーチンに名前を付ける
ルーチンに良い名前がついていることは、素晴らしいプログラムの証
中身のない名前が中身のない設計に起因しているとしたら、それは危険信号(設計を改良する)
今回のReportErrorMessage()ははっきりとした良い名前
4. ルーチンのテスト方法を決定する
だいじ
今回は入力が単純なので、全ての有効なエラーコードといろんな無効なエラーコードを使ってテストする
5. 標準ライブラリが提供している機能を調べる
コードの品質と生産性の両方を改善するとしたら、最も効果的な方法は、良いコードを再利用することである。
6. エラー処理について考える
不正な入力値、他のルーチンから返される無効な値など、ルーチンでエラーが発生する可能性があるものを片っ端から考える
エラーの処理方法を選択する
7. 効率について考える
効率を上げる方法は状況によって2種類に分かれる
1. システムの大部分で効率が重視されないケース
後から改良できるように、ルーチンのインターフェイスの抽象化が十分であるか確認し、ルーチンのコードが理解しやすいか確認する
カプセル化がうまくいっていれば、他のアルゴリズムや他の言語で置き換えたりが可能になる
2. (システムのごく一部で)パフォーマンスが重視されるケース
それぞれのルーチンで使用できるリソースの数と、目安となる処理の速さを規定すべき
akht.iconこんなことしたことないです
↑こういう状況以外で、個々のルーチンのレベルで効率化に取り組むのは通常は無駄な努力である
大きな最適化は、個々のルーチンではなく、上位レベルの設計に磨きをかけることから得られる。
8. アルゴリズムとデータ型について考える
自前で複雑なアルゴリズム書くのやめましょう...楽しいけれど...
9. 擬似コードを書く
ルーチンの概略レベルの擬似コードを書き始める
全体像 → 具体的なものに向かって作業を進める
ルーチンのヘッダーコメントで、そのルーチンの目的を簡潔に説明することから始める
説明文を書くと、自分がルーチンをどの程度理解しているのかが明らかになる
ヘッダーコメントの例
code: ヘッダーコメント
このルーチンは、呼び出し元のルーチンから渡されたエラーコードに基づいて、
エラーメッセージを出力する。メッセージを出力する方法は、
現在の処理状態によって決まる。現在の処理状態は、ルーチンが自動的に取得する。
成功または失敗を示す値を返す。
ルーチンの概略レベルの擬似コードを書く
code: 概略レベルの擬似コード
ステータスの規定値を「fail」に設定する
エラーコードに基づいてメッセージを検索する
if エラーコードが有効
if 対話モードの処理を行なっている
エラーメッセージを対話モードで表示し、成功を宣言する
if コマンドラインモードの処理を行なっている
コマンドラインにエラーメッセージを出力し、成功を宣言する
if エラーコードが無効
内部エラーが検出されたことをユーザーに通知する
ステータス状態を返す
10. データについて考える
データの操作がルーチンの重要な部分である場合は、ルーチンのロジックにつて考える前に、主なデータについて検討する
重要なデータ型の定義は、ルーチンのロジックを設計するときに役立つ
11. 擬似コードを検査する
擬似コードをレビューする
他の人に擬似コードを見てもらうか、自分の説明を聞いてもらう
擬似コードでは、思い込みや概略レベルの誤りがプログラミング言語のコードよりも顕著に現れる
12. 擬似コードでいくつかのアイデアを試し、最も良いものを採用する(反復する)
コーディングに取り掛かる前に、擬似コードで思いつく限りのアイデアを試してみる
いったんコードを書いてしまうと愛着が湧いてしまって悪い設計を捨てて最初からやり直すことが難しくなる
擬似コードの文章が単純なものになるまで、ルーチンを繰り返し検討する
どれくらい単純になればいいか?
元の擬似コードをコメントとして残せるくらいが目安
実際にコーディングする方が時間の節約になると思えるくらいまで、擬似コードに磨きをかけ、分解していくこと
hr.icon
9.3.2 qst_exe.icon
9.3.2 ルーチンのコーディング
https://gyazo.com/a1ce889f93b2d14d35faadefe86f6361
Steve McConnell. 【電子合本版】Code Complete 第2版 完全なプログラミングを目指して (Japanese Edition) (Kindle の位置No.5858). Kindle 版.
ルーチンを宣言する
ルーチンのインターフェイスのステートメントを書く
C++では関数宣言、Javaではメソッド宣言、Visual Basicでは関数またはサブプロシージャの宣言
https://gyazo.com/af7bb3427401106ac673f94e78479363
Steve McConnell. 【電子合本版】Code Complete 第2版 完全なプログラミングを目指して (Japanese Edition) (Kindle の位置No.5864). Kindle 版.
擬似コードを概略レベルのコメントとして転用する
最初と最後のステートメントまで書いて、ルーチンが途切れないようにする。
設計作業が完了した段階で、コードを読まなくてもルーチンの仕組みが理解できるはずで、擬似コードをプログラミング言語のコードに書き換える作業は、簡単にできるはずである。
上記の作業が難しい場合は、設計が安定するまで擬似コードの設計を続ける。
各コメントの下にコードを記入する
擬似コードの下にコードを記入する。
このプロセスは学期末レポートに似ている(概略が擬似コードのコメント、それの詳細な説明がコード)
https://gyazo.com/a32c5c90b007e464e3937dbe0d0c8b74
Steve McConnell. 【電子合本版】Code Complete 第2版 完全なプログラミングを目指して (Japanese Edition) (Kindle の位置No.5881). Kindle 版.
https://gyazo.com/6b794bc3eebc41d3357a71c2eac7e58e
https://gyazo.com/a4afeda1d8cb7977c195c410a7d36a11
Steve McConnell. 【電子合本版】Code Complete 第2版 完全なプログラミングを目指して (Japanese Edition) (Kindle の位置No.5887). Kindle 版.
コードをさらに分解するべきかどうか確認する 最初の擬似コードが大量のコードになってしまった場合には、対処する必要がある
擬似コードの下にあるコードを新しいルーチンに分解する
PPPを再帰的に適用する 1行の擬似コードが大量のコードを生み出す場合は、1行の擬似コードを複数の擬似コードに分解してから、コードに起こす
9.3.3 コードの検査
この段階で見逃したエラーは後のテスト段階まで検知されないので、なるべくこの段階で全てのエラーを洗い出しておく
ルーチンのエラーを頭で確認する 正常なパスとエンドポイント、そしてすべての例外状態を確認する
自分1人で行う検査を机上検査、複数人で行う場合は、ピアレビュー、ウォークスルー、インスペクションなどと呼ばれる
結論:ルーチンが動いているだけでは十分ではない。それが動いている理由がわからなければ、それを理解できるまで調査し、議論し、他の設計を試してみること。
ルーチンをコンパイルする
「あともう1回だけコンパイル」症候群を避けるために、エラーとなる原因を先に潰してからコンパイルする
「ハック、コンパイル、修正」というフローから脱却しないといけない
安易な修正をしなければコンパイルしてもよさそう?コンパイルする前にわかることも多いかも
バリデーターとかの話につながる(コンパイルは使わない方向で、、、)
コンパイルの警告レベルをできるだけ厳しくする
バリデータを使用する
lintで補うことができるから対応すること
全てのエラーメッセージと警告の原因を排除する
Xcodeのエラーは無視しがち…qst_exe.icon
デバッガでコードを実行する
コードをテストする
ルーチンからエラーを取り除く
ルーチンがバグだらけであることに気づいたら、ハックしようとはせずに最初からやり直す
9.3.4 残りの仕上げ作業
ルーチンのインターフェイスを検査する
入力データと出力データがすべて明確になっていること、全ての引数が使用されることを確認する
全体的な設計の品質を検査する
ルーチンが1つのことを確実に行い、他のルーチンとの結合度が弱く、防御的に設計されていることを確認する
ルーチンの変数を検査する
不正確な変数名、使用されていないオブジェクト、宣言されていない変数、正しく初期化されていないオブジェクトがないかどうかを確認する
ルーチンのステートメントとロジックを検査する
1つ違いエラー、無限ループ、不適切なネスト、リソースのリークを確認する
ルーチンのレイアウトを検査する
余白を利用して、ルーチン、式、引数リストの論理構造が明確になっていることを確認する(詳細は31章で)
ルーチン内のドキュメントを検査する
コメントに書き換えた擬似コードが正確さを失っていないことを確認する。
アルゴリズムの説明、インターフェイスの条件や自明でない依存関係について説明するドキュメント、不明確なコーディングプラクティスが使われている理由について説明するコメントを確認する(詳細は32章で)
重複したコメントを削除する
PPPが再帰的に適用されていたり、分かりやすい名前がついたコードを呼び出す直前にあるコメントは特に重複しやすい
9.3.5 必要に応じて繰り返す
ルーチンの品質が悪い場合は、擬似コードに戻る。
高品質なプログラミングとは、反復型のプロセスであるため、コンストラクションの作業は何度でも納得がいくまで繰り返す
9.4 PPP以外の方法
テストファースト開発
コードを書く前にテストケースを書くという開発手法
実際に取り入れているところはあるのだろうか
前に受講したTDD体験会がとてもよかったqst exe.icon
youtubeのライブコーディングやってましたねmoch5oMaki.icon
https://www.youtube.com/watch?v=Q-FJ3XmFlT8
リファクタリング
コードの意味を保ったままコードを変換して、コードを改善する開発手法
プログラマは悪いコードのパターンまたは「におい」で、改善の余地があるコードを嗅ぎ分ける
なかなか難しい
契約による設計
各ルーチンに事前条件と事後条件があるものとを考える開発手法
8.2.2 で解説済み
ハック
コードをハックして動かすとは? qst_exe.icon
(チェックリストは書籍参照)
9.5 まとめ
クラスやルーチンの作成は反復型のプロセスであることが多い。
特定のルーチンを作成している最中に得られた洞察力は、クラス設計に活かされる
良い擬似コードを書くには、分かりやすい文章を使い、特定のプログラミング言語に固有の機能をさけ、目的のレベルで書く必要がある
(array_filterで〇〇して等という書き方はしない)
PPPは、詳細設計のために便利なツールであり、コーディングを容易にする。
擬似コードはそのままコメントになるため、正確かつ効果的なコメントが得られる
最初に思いついた設計で満足しない。
コーディングに取りかかる前に、擬似コードを使って複数の方法を試し、最も良い方法を選ぶ。
段階ごとにそこでの成果物を確認し、第三者にも確認してもらう。
そうすれば、まだ労力をそれほど注ぎ込んでいない最も安価な段階で、ミスを捕らえることができる。